package wrice.esm.compiler;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

import wrice.esm.compiler.Parser.Events;
import wrice.esm.compiler.Parser.Events.Event;

public class JavaGenerator implements Generator {

	private static final String FSMTemplateJava = "./bin/wrice/esm/compiler/FSMTemplate.txt";
	private static final String StateTemplateJava = "./bin/wrice/esm/compiler/StateTemplate.txt";
	private static final String EventHandlerTemplateJava = "./bin/wrice/esm/compiler/EventHandlerTemplate.txt";
	private static final String EventTemplateJava = "./bin/wrice/esm/compiler/EventTemplate.txt";
	public static final String newline = System.getProperty("line.separator");
	public static final String currentWorkingDir = System.getProperty("user.dir");
	
	public JavaGenerator(final Parser parser, String outputFolder)
	{
		String packageName = parser.globalVariables_.get("PackageName".toLowerCase());
		String actor = parser.globalVariables_.get("Actor".toLowerCase());
		String initialState = parser.globalVariables_.get("InitialState".toLowerCase());

		if( null == packageName ) throw new InvalidStateMachineDefinitionRuntimeException();
		if( null == actor )  throw new InvalidStateMachineDefinitionRuntimeException();
		if( null == initialState ) throw new InvalidStateMachineDefinitionRuntimeException();
		
		final String FSMPackageName = packageName + ".fsm";
		final String eventPackageName = packageName + ".fsm.event";
		final String FSMClassName = actor + "FSM";
		final String FSMFilename = outputFolder.replaceAll("\\\\", "/") + "/fsm/" + FSMClassName + ".java";
		final String eventBasename = outputFolder.replaceAll("\\\\", "/") + "/fsm/event/";
		
		final String FSMRawTemplate = getLoadFile( FSMTemplateJava );
		final String stateRawTemplate = getLoadFile( StateTemplateJava );
		final String eventHandlerRawTemplate = getLoadFile( EventHandlerTemplateJava );
		final String eventRawTemplate = getLoadFile( EventTemplateJava );

		String stateVariableDeclaration = "";
		String stateSetCode = "";
		Set<String> eventNames = new HashSet<String>();
		for (Entry<String, Events> stateEntry : parser.states_.entrySet())
		{
		    String stateName = stateEntry.getKey();
		    Events stateEventSet = stateEntry.getValue();
		    
		    String eventHandlerSetCode = "";
			for (Entry< String, Event > eventEntry : stateEventSet.events_.entrySet())
			{
			    String stateEventName = eventEntry.getKey();
			    Event stateEvent = eventEntry.getValue();
			    eventNames.add( stateEventName );

			    String eventHandlerCode = eventHandlerRawTemplate;
			    eventHandlerCode = eventHandlerCode.replaceAll("/\\*NextState\\*/", stateEvent.nextState_ );
				eventHandlerCode = eventHandlerCode.replaceAll("/\\*EventName\\*/", stateEventName );
				eventHandlerCode = eventHandlerCode.replaceAll("/\\*NextStateVariable\\*/", getStateVariableName( stateEvent.nextState_ ) );
				if( null != stateEvent.action_ )
					eventHandlerCode = eventHandlerCode.replaceAll("//actor_./\\*Action\\*/\\( evt \\);",  "actor_." + stateEvent.action_ + "(evt);" );
				if( stateName.equals( stateEvent.nextState_) )
				{
					eventHandlerCode = eventHandlerCode.replaceAll( "//return ", "return this; // " );
				}
				else
				{
					eventHandlerCode = eventHandlerCode.replaceAll( "//currentState_ = ", "currentState_ = " );
					eventHandlerCode = eventHandlerCode.replaceAll( "//return ", "return " );
				}
				eventHandlerSetCode += eventHandlerCode + newline;
			}
			
			String stateCode = stateRawTemplate;
			stateCode = stateCode.replaceAll("/\\*StateClassName\\*/", stateName + "State" );
			stateCode = stateCode.replaceAll("/\\*EventHandler\\*/", eventHandlerSetCode );
			stateSetCode += stateCode + newline;
			
			stateVariableDeclaration += "	private " + stateName + "State " + getStateVariableName( stateName ) + " = new " + stateName + "State();" + newline;
		}

		String FSMCode = FSMRawTemplate + newline;
		FSMCode = FSMCode.replaceAll("/\\*FSMPackageName\\*/", FSMPackageName );
		FSMCode = FSMCode.replaceAll("/\\*PackageName\\*/", packageName );
		FSMCode = FSMCode.replaceAll("/\\*Actor\\*/", actor );
		FSMCode = FSMCode.replaceAll("/\\*FSMName\\*/", FSMClassName );
		FSMCode = FSMCode.replaceAll("/\\*InitialState\\*/", initialState );
		FSMCode = FSMCode.replaceAll("/\\*InitialStateVariable\\*/", getStateVariableName( initialState ) );
		FSMCode = FSMCode.replaceAll("/\\*StateVariableDeclaration\\*/", stateVariableDeclaration );
		FSMCode = FSMCode.replaceAll("/\\*StateClassDeclaration\\*/", stateSetCode );

		writeFile(FSMFilename, FSMCode);

		for( String eventName : eventNames )
		{
			String filename = eventBasename + eventName + "Event.java";
			File file = new File( filename );
			if( file.exists() ) continue;

			String eventCode = eventRawTemplate + newline;
			eventCode = eventCode.replaceAll( "/\\*EventPackageName\\*/", eventPackageName );
			eventCode = eventCode.replaceAll( "/\\*EventName\\*/", eventName );
			writeFile( filename, eventCode );
		}
		
	}

	private void writeFile(String filename, String code) {
		String basedirname = filename.substring(0, filename.lastIndexOf('/') );
		
		File basedir = new File( basedirname );
		if( !basedir.exists() ) basedir.mkdirs();

		PrintWriter pw = null;
		try {
			pw = new PrintWriter(filename);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException( "File cannot be written on " + filename );
		}
		
		pw.println(code);

		pw.close();
	}
	
	private String getLoadFile(String filename) {
		File file = new File( filename );
		
		Scanner scanner = null;
		try {
			scanner = new Scanner( file );
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException( "File not found: " + e.getMessage() );
		}
		
		String wholeFile = "";
		while( scanner.hasNext() )
			wholeFile += scanner.nextLine() + newline;
		return wholeFile;
	}

	String getStateVariableName( String stateName )
	{
		return stateName.toLowerCase().substring(0, 1) + stateName.substring(1) + "State_";
	}

}
